package binaryfile

//加密内容并保存到文件

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
)

//File 文件信息
type File struct {
	Key     string
	Path    string
	Content []byte
}

func isfile(f string) bool {
	fi, e := os.Stat(f)
	if e != nil {
		return false
	}
	return !fi.IsDir()
}

//Save 保存内容到文件，会覆盖原有内容
func (d *File) Save() error {

	content, err := Encode(d.Content, []byte(d.Key))
	if err != nil {
		return err
	}

	file, err := os.OpenFile(d.Path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	defer file.Close()
	if err != nil {
		return err
	}

	writer := bufio.NewWriter(file) //创建一个writer,带缓存
	_, err = writer.Write(content)  //写入
	if err != nil {
		return err
	}
	return writer.Flush()

}

//Read 读取文件全部内容
func (d *File) Read() error {
	if !isfile(d.Path) {
		e := fmt.Sprintf("%s文件不存在", d.Path)
		return errors.New(e)
	}

	file, err := os.Open(d.Path) //打开文件
	defer file.Close()           //在返回前关闭打开的文件
	if err != nil {              //打开文件出错时
		return err
	}
	content := []byte{}
	reader := bufio.NewReader(file) //创建一个 *Reader ， 是带缓冲的

	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		content = append(content, line...)
	}

	d.Content, err = Decode(content, []byte(d.Key))

	if err != nil {
		return err
	}
	return nil
}

func Encode(src, key []byte) ([]byte, error) {
	//b := Base64Encode(src)
	pkey := Md5(key)
	code, err := AesCBCEncrypt(src, []byte(pkey))
	if err != nil {
		return nil, err
	}
	return Base64Encode(code), nil
}

func Decode(src, key []byte) ([]byte, error) {
	pkey := Md5(key)
	b, err := Base64Decode(src)
	if err != nil {
		return nil, err
	}
	return AesCBCDncrypt(b, []byte(pkey))
}

func Base64Encode(src []byte) []byte {
	maxLen := base64.StdEncoding.EncodedLen(len(src))
	dst := make([]byte, maxLen)
	base64.StdEncoding.Encode(dst, src)
	return dst
}

func Base64Decode(src []byte) ([]byte, error) {
	maxLen := base64.StdEncoding.DecodedLen(len(src))
	dst := make([]byte, maxLen)
	n, err := base64.StdEncoding.Decode(dst, src)
	if err != nil {
		return nil, err
	}
	return dst[:n], nil
}

func Md5(src []byte) string {
	h := md5.New()
	h.Write(src)
	return hex.EncodeToString(h.Sum(nil))
}

// PKCS7Padding 使用PKCS7进行填充，IOS也是7
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

//PKCS7UnPadding unpadding
func PKCS7UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

//AesCBCEncrypt 加密，填充秘钥key的16位，24,32分别对应AES-128, AES-192, or AES-256.
func AesCBCEncrypt(rawData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	//填充原文
	blockSize := block.BlockSize()
	rawData = PKCS7Padding(rawData, blockSize)
	//初始向量IV必须是唯一，但不需要保密
	cipherText := make([]byte, blockSize+len(rawData))
	//block大小 16
	iv := cipherText[:blockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	//block大小和初始向量大小一定要一致
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(cipherText[blockSize:], rawData)

	return cipherText, nil
}

//AesCBCDncrypt 解密
func AesCBCDncrypt(encryptData, key []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	blockSize := block.BlockSize()

	if len(encryptData) < blockSize {
		panic("ciphertext too short")
	}
	iv := encryptData[:blockSize]
	encryptData = encryptData[blockSize:]

	// CBC mode always works in whole blocks.
	if len(encryptData)%blockSize != 0 {
		panic("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)

	// CryptBlocks can work in-place if the two arguments are the same.
	mode.CryptBlocks(encryptData, encryptData)
	//解填充
	encryptData = PKCS7UnPadding(encryptData)
	return encryptData, nil
}
